﻿using System;
using System.Collections.Generic;
using System.Text;
using Server.Items;
using Server.Targeting;

namespace Server.Engines.Harvest
{
	public static class Fishing
	{
		private static HarvestBankList m_banks;
		private static int[] m_tiles;

		static Fishing()
		{
			m_tiles = new int[] { 0x00A8, 0x00AB, 0x0136, 0x0137, 0x5797, 0x579C, 0x746E, 0x7485, 0x7490, 0x74AB, 0x74B5, 0x75D5 };
			m_banks = new HarvestBankList(8, 8, 15, 30, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(20));
		}

		/// <summary>
		/// Vérifie si l'outils est utilisable (Durabilité).
		/// Vérifie si le joueur n'est pas sur une monture.
		/// Vérifie si l'outils est équipé.
		/// </summary>
		/// <param name="from">Le joueur</param>
		/// <param name="tool">L'outils utilisé</param>
		/// <returns>True is les vérifications sont correct. False sinon.</returns>
		private static bool CheckFishing(Mobile from, FishingTool tool)
		{
			if (from.Mounted)
			{
				from.SendMessage("Vous ne pouvez pas pêcher sur une monture !");
				return false;
			}
			if (tool.UsesRemaining <= 0)
			{
				from.SendMessage("Votre outils est cassé !");
				return false;
			}
			if (tool.Parent != from)
			{
				from.SendMessage("Vous devez équiper cet objet pour pêcher !");
				return false;
			}
			if (tool.Deleted)
			{
				from.SendMessage("L'objet a été supprimer !");
				return false;
			}

			return true;
		}

		/// <summary>
		/// Vérifie si le joueur est à portée de la zone de collecte
		/// </summary>
		/// <param name="from">Le joueur</param>
		/// <param name="loc">La zone de collecte</param>
		/// <param name="map">La carte où se trouve le joueur et la zone de collecte</param>
		/// <param name="tool">L'outils utilisé</param>
		/// <returns>True is les vérifications sont correct. False sinon.</returns>
		public static bool CheckRange(Mobile from, IPoint3D loc, Map map, FishingTool tool)
		{
			if (from.Map != map || !from.InRange(loc, tool.MaxRange))
			{
				from.SendMessage("Vous n'êtes pas à portée !");
				return false;
			}
			return true;
		}

		/// <summary>
		/// Vérifie si le joueur peut pêcher.
		/// Demande une cible de pêche au joueur.
		/// </summary>
		/// <param name="from">Le joueur</param>
		/// <param name="tool">L'outils utilisé</param>
		public static void BeginFishing(Mobile from, FishingTool tool)
		{
			if (!CheckFishing(from, tool))
				return;

			from.Target = new FishingTarget(tool);
			from.SendMessage("Où souhaitez vous pêcher ?");
		}

		/// <summary>
		/// Débute l'activité de pêche sur la cible désigné par le joueur.
		/// </summary>
		/// <param name="from">Le joueur</param>
		/// <param name="tool">L'outils utilisé</param>
		/// <param name="target">La cible de l'outils de pêche</param>
		public static void StartFishing(Mobile from, FishingTool tool, object target)
		{
			if (!CheckFishing(from, tool))
				return;

			int tileID;
			Map map;
			Point3D loc;

			if (!HarvestHelper.GetTargetDetails(from, tool, target, out tileID, out map, out loc))
				return;

			if (!ValidateTarget(tileID, true))
				return;

			if (!CheckDeepWater(from, tool, map, loc))
				return;

			if (!CheckRange(from, loc, map, tool))
				return;

			if (!m_banks.CheckResources(from, map, loc))
				return;

			object toLock = HarvestHelper.GetLock(from, tool, typeof(Fishing));

			if (!from.BeginAction(toLock))
			{
				from.SendMessage("Vous êtes déjà occupé !");
				return;
			}

            DoEffects(from, map, loc, tool);
			new FishingTimer(from, tool, target, toLock, true).Start();
		}

		/// <summary>
		/// Vérifie si l'outils est adapté pour pêcher à l'endroit spécifié
		/// </summary>
		/// <param name="from">Le joueur</param>
		/// <param name="map">La carte où se trouve le joueur et la zone de collecte</param>
		/// <param name="map">La carte où se trouve le joueur et la zone de collecte</param>
		/// <param name="loc">La zone de collecte</param>
		/// <returns></returns>
		public static bool CheckDeepWater(Mobile from, FishingTool tool, Map map, Point3D loc)
		{
			if (tool.UseDeepWater && !IsFullDeepWater(map, loc.X, loc.Y))
			{
				from.SendMessage("Cet outils est fait pour pêcher en eau profonde !");
				return false;
			}
			if (!tool.UseDeepWater && IsFullDeepWater(map, loc.X, loc.Y))
			{
				from.SendMessage("Cet outils n'est pas fait pour pêcher en eau profonde !");
				return false;
			}
			return true;
		}

		/// <summary>
		/// Joue l'effets et le son
		/// </summary>
		/// <param name="from">Le joueur</param>
		/// <param name="map">La carte où se trouve le joueur et la zone de collecte</param>
		/// <param name="loc">La zone de collecte</param>
		private static void DoEffects(Mobile from, Map map, Point3D loc, FishingTool tool)
		{
			Timer.DelayCall(TimeSpan.FromSeconds(1.5), delegate
			{
				from.RevealingAction();
				Effects.SendLocationEffect(loc, map, 0x352D, 16, 4);
				Effects.PlaySound(loc, map, 0x364);
			});

			from.Direction = from.GetDirectionTo(loc);
			from.Animate(tool.Animation, 5, 1, true, false, 0);
		}

		/// <summary>
		/// Termine la pêche et détermine le début et les skills / attributs gagnés
		/// </summary>
		/// <param name="from">Le joueur</param>
		/// <param name="tool">L'outils utilisé</param>
		/// <param name="target">La cible de l'outils de pêche</param>
		/// <param name="locked">Lock permettant de restreindre l'activité du joueur à celle en cours</param>
		public static void FinishFishing(Mobile from, FishingTool tool, object target, object locked)
		{
			from.EndAction(locked);

			if (!CheckFishing(from, tool))
				return;

			int tileID;
			Map map;
			Point3D loc;

			if (!HarvestHelper.GetTargetDetails(from, tool, target, out tileID, out map, out loc))
				return;

			if (!ValidateTarget(tileID, true))
				return;

			if (!CheckRange(from, loc, map, tool))
				return;
			if (!m_banks.CheckResources(from, map, loc))
				return;

			FishBank bank = m_banks.GetBank(map, loc.X, loc.Y);

			if (bank == null)
				return;

			double skillBase = from.Skills[SkillName.Fishing].Base;
			double skillValue = from.Skills[SkillName.Fishing].Value;

			Type type = GetResource(from, tool, map, loc);

			if (type != null)
			{
				Fish fish = HarvestHelper.Construct(type, from) as Fish;

				bank.Consume(fish.Amount, from);

				if (HarvestHelper.Give(from, fish, true))
				{
					from.SendMessage("Vous avez pêché 1 {0}", fish.Name);
				}
				else
				{
					from.SendMessage("Vous n'avez pas assez de place dans votre sac !");
					fish.Delete();
				}
			}

			if (type == null)
				from.SendMessage("Vous n'avez rien pêché...");

			if (Core.ML)
				from.RevealingAction();
		}

		/// <summary>
		/// Récupère le poisson pêcher en fonction des skill du joueurs. Si aucun poisson, retour NULL.
		/// </summary>
		/// <param name="from">Le joueur</param>
		/// <param name="tool">L'outils utilisé</param>
		/// <param name="map">La carte où se trouve le joueur et la zone de collecte</param>
		/// <param name="loc">La zone de collecte</param>
		public static Type GetResource(Mobile from, FishingTool tool, Map map, Point3D loc)
		{
			double skillBase = from.Skills[SkillName.Fishing].Base;
			double skillValue = from.Skills[SkillName.Fishing].Value;

            double chance = ((skillBase > 0 ? skillBase : 1) / 100) * tool.Chance;

			//CheckSkill gère le gain de niveau automatiquement
			if (from.CheckSkill(SkillName.Fishing, chance) && chance > Utility.RandomDouble())
			{
				UseTool(from, tool);

				double range = Utility.RandomDouble();

				for (int i = 0; i < tool.AvailableFishes.Length; ++i)
				{
					FishEntry entry = tool.AvailableFishes[i];
					if (range <= entry.Range)
						return entry.Types[Utility.Random(entry.Types.Length)];
				}
			}
			return null;
		}

		/// <summary>
		/// Use l'outils utilisé. Si celui ci n'a plus de durabilité, l'outils est détruit
		/// </summary>
		/// <param name="from">Le joueur</param>
		/// <param name="tool">L'outils utilisé</param>
		public static void UseTool(Mobile from, FishingTool tool)
		{
			if (tool.UsesRemaining > 0)
				--tool.UsesRemaining;
			if (tool.UsesRemaining < 1)
			{
				tool.Delete();
				from.SendMessage("Votre outils est cassé !");
			}
		}

		/// <summary>
		/// Vérifie si le tile aux coordonées x et y, et bien entouré d'eau profonde.
		/// </summary>
		/// <param name="map">La carte où se trouve le joueur et la zone de collecte</param>
		/// <param name="x">Coordonnées X de la cible</param>
		/// <param name="y">Coordonnées Y de la cible</param>
		public static bool IsFullDeepWater(Map map, int x, int y)
		{
			bool valid = IsDeepWater(map, x, y);

			for (int j = 1, offset = 5; valid && j <= 5; ++j, offset += 5)
			{
				if (!IsDeepWater(map, x + offset, y + offset))
					valid = false;
				else if (!IsDeepWater(map, x + offset, y - offset))
					valid = false;
				else if (!IsDeepWater(map, x - offset, y + offset))
					valid = false;
				else if (!IsDeepWater(map, x - offset, y - offset))
					valid = false;
			}

			return valid;
		}

		/// <summary>
		/// Vérifie si le tile aux coordonées x et y, et une eau profonde.
		/// </summary>
		/// <param name="map">La carte où se trouve le joueur et la zone de collecte</param>
		/// <param name="x">Coordonnées X de la cible</param>
		/// <param name="y">Coordonnées Y de la cible</param>
		public static bool IsDeepWater(Map map, int x, int y)
		{
			int tileID = map.Tiles.GetLandTile(x, y).ID;
			bool water = false;

			for (int i = 0; !water && i < m_tiles.Length; i += 2)
				water = (tileID >= m_tiles[i] && tileID <= m_tiles[i + 1]);

			return water;
		}

		/// <summary>
		/// Vérifie si le tile de la cible est bien une zone d'eau
		/// </summary>
		/// <param name="tileID">ID du tile de la zone cible</param>
		/// <param name="ranged">???</param>
		public static bool ValidateTarget(int tileID, bool ranged)
		{
			if (ranged)
			{
				bool contains = false;

				for (int i = 0; !contains && i < m_tiles.Length; i += 2)
					contains = (tileID >= m_tiles[i] && tileID <= m_tiles[i + 1]);

				return contains;
			}
			else
			{
				int dist = -1;

				for (int i = 0; dist < 0 && i < m_tiles.Length; ++i)
					dist = (m_tiles[i] - tileID);

				return (dist == 0);
			}
		}
	}

	public class FishingTarget : Target
	{
		private FishingTool m_tool;

		public FishingTarget(FishingTool tool)
			: base(-1, true, TargetFlags.None)
		{
			DisallowMultis = true;
			m_tool = tool;
		}

		protected override void OnTarget(Mobile from, object targeted)
		{
			Fishing.StartFishing(from, m_tool, targeted);
		}
	}

	public class FishingTimer : Timer
	{
		private Mobile m_from;
		private FishingTool m_tool;
		private object m_target, m_locked;
		private bool m_last;

		public FishingTimer(Mobile from, FishingTool tool, object target, object locked, bool last)
			: base(tool.Delay)
		{
			m_from = from;
			m_tool = tool;
			m_target = target;
			m_locked = locked;
			m_last = last;
		}

		protected override void OnTick()
		{
			if (m_last)
				Fishing.FinishFishing(m_from, m_tool, m_target, m_locked);
		}
	}

	public class HarvestBankList
	{
		private Dictionary<Map, Dictionary<Point2D, FishBank>> m_banks;
		private int m_bankWidth;
		private int m_bankHeight;
		private int m_minResources;
		private int m_maxResources;
		private TimeSpan m_minRespwawn;
		private TimeSpan m_maxRespwawn;

		public HarvestBankList(int bankWidth
			, int bankHeight
			, int minResources
			, int maxResources
			, TimeSpan minRespwawn
			, TimeSpan maxRespwawn)
		{
			m_banks = new Dictionary<Map, Dictionary<Point2D, FishBank>>();
			m_bankWidth = bankWidth;
			m_bankHeight = bankHeight;
			m_minResources = minResources;
			m_maxResources = maxResources;
			m_minRespwawn = minRespwawn;
			m_maxRespwawn = maxRespwawn;
		}

		public FishBank GetBank(Map map, int x, int y)
		{
			if (map == null || map == Map.Internal)
				return null;

			x /= m_bankWidth;
			y /= m_bankHeight;

			Dictionary<Point2D, FishBank> banks = null;
			m_banks.TryGetValue(map, out banks);

			if (banks == null)
				m_banks[map] = banks = new Dictionary<Point2D, FishBank>();

			Point2D key = new Point2D(x, y);
			FishBank bank = null;
			banks.TryGetValue(key, out bank);

			if (bank == null)
				banks[key] = bank = new FishBank(Utility.RandomMinMax(m_minResources, m_maxResources), m_minRespwawn, m_maxRespwawn);

			return bank;
		}

		public bool CheckResources(Mobile from, Map map, Point3D loc)
		{
			FishBank bank = GetBank(map, loc.X, loc.Y);
			if (bank == null || bank.Current <= 0)
			{
				from.SendMessage("Il ne semble plus y avoir grand chose ici !");
				return false;
			}
			return true;
		}
	}

	public class FishBank
	{
		private int m_current;
		private int m_maximum;
		private TimeSpan m_minRespwawn;
		private TimeSpan m_maxRespwawn;
		private DateTime m_nextRespawn;

		public int Current
		{
			get
			{
				CheckRespawn();
				return m_current;
			}
		}

		public FishBank(int maximum, TimeSpan minRespawn, TimeSpan maxRespawn)
		{
			m_maximum = maximum;
			m_current = maximum;
			m_minRespwawn = minRespawn;
			m_maxRespwawn = maxRespawn;
		}

		public void CheckRespawn()
		{
			if (m_current == m_maximum || m_nextRespawn > DateTime.Now)
				return;

			m_current = m_maximum;
		}

		public void Consume(int amount, Mobile from)
		{
			CheckRespawn();

			if (m_current == m_maximum)
			{
				double min = m_minRespwawn.TotalMinutes;
				double max = m_maxRespwawn.TotalMinutes;
				double rnd = Utility.RandomDouble();

				m_current = m_maximum - amount;

				double minutes = min + (rnd * (max - min));

				m_nextRespawn = DateTime.Now + TimeSpan.FromMinutes(minutes);
			}
			else
			{
				m_current -= amount;
			}

			if (m_current < 0)
				m_current = 0;
		}
	}
}
